package com.github.tommas.admintpl.controller;

import com.github.tommas.admintpl.bean.model.FileInfoModel;
import com.github.tommas.admintpl.bean.model.User;
import com.github.tommas.admintpl.bean.vo.File;
import com.github.tommas.admintpl.common.CommonResult;
import com.github.tommas.admintpl.security.UserPrincipal;
import com.github.tommas.admintpl.security.permission.RestrictedPermission;
import com.github.tommas.admintpl.security.permission.restriction.Restriction;
import com.github.tommas.admintpl.service.FileStorageService;
import com.github.tommas.admintpl.service.PermissionRestrictionService;
import com.github.tommas.admintpl.service.UserService;
import com.github.tommas.admintpl.service.exception.FileStorageException;
import com.github.tommas.admintpl.util.PermissionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;

public abstract class BaseController {

    @Autowired
    FileStorageService fileStorageService;

    @Autowired
    UserService userService;

    @Autowired
    PermissionRestrictionService restrictionService;

    protected CommonResult<List<FileInfoModel>> processFileUpload(Integer targetId, String businessType, MultipartFile ...mfiles) throws IOException {
        try {
            return CommonResult.success(doProcessFileUpload(targetId, businessType, 0, false, mfiles));
        } catch (FileStorageException e) {
            return CommonResult.failed(String.format("Failed to store files: %s", e.getFailedFiles()), null);
        }
    }

    /**
     * @param targetId target id or null
     * @throws IOException
     */
    protected List<FileInfoModel> doProcessFileUpload(Integer targetId, String businessType, long timeout, boolean allowPartialStore, MultipartFile ...mfiles) throws IOException {
        if (mfiles.length == 0) {
            return Collections.emptyList();
        }

        User user = getCurrentLoginUser();
        Integer deptId = user.getDeptId();

        if (mfiles.length == 1) {
            MultipartFile file = mfiles[0];
            return Collections.singletonList(
                    fileStorageService.store(file.getInputStream(), file.getOriginalFilename(), (int) file.getSize(), targetId, businessType, deptId));
        }

        File[] files = new File[mfiles.length];
        for (int i = 0; i < mfiles.length; i++) {
            File file = new File();
            file.setName(mfiles[i].getOriginalFilename());
            file.setSize((int) mfiles[i].getSize());
            file.setInputStream(mfiles[i].getInputStream());
            files[i] = file;
        }

        return fileStorageService.storeInBatch(files, targetId, businessType, deptId, timeout, allowPartialStore);
    }

    @FunctionalInterface
    public interface FileTargetValidator {
        boolean validate(Integer targetId);

        default String getErrorMessage() {
            return "File target validation error";
        }
    }

    protected void processFileDownload(Integer targetId, String businessType, Integer fileId,
                                       HttpServletResponse response, FileTargetValidator validator) throws IOException {
        if (validator != null) {
            if (!validator.validate(targetId)) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getOutputStream().print(validator.getErrorMessage());
                return;
            }
        }

        File file = fileStorageService.getFile(fileId, targetId, businessType);
        if (file == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // TODO: set content type according to the file name
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment()
                .filename(file.getName(), StandardCharsets.UTF_8)
                .build()
                .toString());

        if (file.getSize() != null) {
            response.setContentLength(file.getSize());
        }

        try (InputStream input = file.getInputStream();
             OutputStream out = response.getOutputStream()) {
            IOUtils.copy(input, out);
            out.flush();
        }
    }

    protected Restriction getAuthorizedPermissionRestriction(String restrictionName) {
        RestrictedPermission permission = PermissionUtils.getAuthorizedPermission();
        if (permission.isRestrictedWith(restrictionName)) {
            return resolvePermissionRestriction(restrictionName);
        }

        return null;
    }

    private Restriction resolvePermissionRestriction(String restrictionName) {
        UserPrincipal userPrincipal = UserPrincipal.fromPrincipalCollection(SecurityUtils.getSubject().getPrincipals());
        switch (restrictionName) {
            case Restriction.DEPT_ISOLATION:
                return restrictionService.getDeptIsolationRestriction(userPrincipal.getUserId());
            default:
                throw new RuntimeException(String.format("Unknown restriction name: %s", restrictionName));
        }
    }

    protected User getCurrentLoginUser() {
        Subject subject = SecurityUtils.getSubject();
        UserPrincipal principal = UserPrincipal.fromPrincipalCollection(subject.getPrincipals());

        // TODO: use CacheManager to cache user

        return userService.findUser(principal.getUserId());
    }
}
